package com.meshnetics.gb.stack.mac;

import com.meshnetics.gb.stack.core.event.StackEvent;
import com.meshnetics.gb.stack.core.event.system.FailureStackEvent;
import com.meshnetics.gb.stack.core.event.system.SystemCodes;
import com.meshnetics.gb.util.ToStringUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

/**
 * Reader of frames from underlaying InputStream. InptStream expected to be blocking
 * (performes blocking for read operations if not enough bytes in stream). Frames read from stream are
 * deserialized to MAC primitives via factory. On any error (exception) frame reader sends FailureEvent to
 * RadioMACStackLayer.
 *
 * @author AKovylin
 */
class RadioMACFrameReader extends Thread {
    private static final Log log = LogFactory.getLog(RadioMACFrameReader.class);

    private final InputStream inputStream;
    private final PrimitivesFactory factory;
    private final RadioMACStackLayer macLayer;

    RadioMACFrameReader(InputStream inputStream, PrimitivesFactory factory, RadioMACStackLayer macLayer) {
        // blocking stream expected
        this.inputStream = inputStream;
        this.factory = factory;
        this.macLayer = macLayer;
    }

    public void run() {
        if (log.isInfoEnabled()) {
            log.info("Event reader started..");
        }
        try {
            while (!isInterrupted()) {
                byte[] bf = readNextFrame();
                if (bf != null) {
                    parseFrame(bf);
                }
            }
        } catch (IOException e) {
            macLayer.acceptEvent(new FailureStackEvent(
                    ToStringUtil.getSimpleName(e.getClass()), SystemCodes.FAILURE_LAYER_GENERAL, e));
        } catch (InterruptedException e) {
            if (log.isDebugEnabled()) {
                log.debug("interrupted", e);
            }
        } catch (Throwable th) {
            macLayer.acceptEvent(new FailureStackEvent(
                    ToStringUtil.getSimpleName(th.getClass()), SystemCodes.FAILURE_LAYER_GENERAL, th));
        } finally {
            if (log.isInfoEnabled()) {
                log.info("Event reader stopped...");
            }
            // if we are stopping - RadioMACStackLayer will only log this event and not pass it anywhere else
            macLayer.acceptEvent(new FailureStackEvent("InterruptedException",
                    SystemCodes.FAILURE_LAYER_GENERAL, new InterruptedException("RadioMACFrameReader thread done.")));
        }
    }

    private byte[] readNextFrame() throws IOException {
        if (log.isDebugEnabled()) {
            log.debug("waiting for new frame...");
        }
        int len = inputStream.read();
        byte[] bf = new byte[len];
        if (log.isDebugEnabled()) {
            log.debug("reading " + len + " bytes frame");
        }
        /*int current = 0;
        while (current < len) {
            current += inputStream.read(bf, current, len - current);
        }*/
        int n = inputStream.read(bf); // we expect blocking stream that will block until whole bf is filled
        // less than len bytes maybe read if EOF reached or exception thrown after first byte read successfuly
        // moreover UARTConnectionFactory creates blocking InputStream regardless underlaying InputStream capabilities
        if (n != len) {
            Exception e = new IOException(len + "bytes expected, but only " + n + " bytes read");
            macLayer.acceptEvent(new FailureStackEvent(ToStringUtil.getSimpleName(e.getClass()),
                    SystemCodes.FAILURE_LAYER_GENERAL, e));
            return null;
        } else {
            if (log.isDebugEnabled()) {
                log.debug("received frame bytes: " + ToStringUtil.toString(bf));
            }
            return bf;
        }
    }

    private void parseFrame(byte[] bf) throws InterruptedException {
        StackEvent event = null;
        try {
            event = factory.fromByteBuffer(ByteBuffer.wrap(bf).asReadOnlyBuffer().order(ByteOrder.LITTLE_ENDIAN));
            if (log.isDebugEnabled()) {
                log.debug("received event " + event);
            }
        } catch (Exception e) {
            log.error("invalid frame: " + ToStringUtil.toString(bf), e);
            Exception ex = (Exception)
                    new IOException("invalid frame: " + ToStringUtil.toString(bf)).initCause(e);
            macLayer.acceptEvent(new FailureStackEvent(
                    ToStringUtil.getSimpleName(ex.getClass()), SystemCodes.FAILURE_LAYER_GENERAL, ex));
        }
        if (event != null) {
            macLayer.acceptEvent(event);
        }
    }
}
